# Fixme: This version can't deal with the socket error or exception after connection has been established
import struct
import sys
import os
import socket
import subprocess
import time

from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QWidget, QApplication, QButtonGroup
from EngineerLogIn import Ui_Form as EngineerLogInUi
from EngineerController import Ui_Form as EngineerControllerPanelUi
from WarningDialog import Ui_Form as ErrorDialog
from EngineerViewStatus import Ui_Form as EngineerViewStatusUi
from send_file import send_file
from EngineerSetParams import Ui_Form as EngineerSetParamsUi


def flush_sock_pipe(sock: socket.socket):
    try:
        sock.recv(1024, socket.MSG_DONTWAIT)
    except OSError as e:
        pass


class DialogWidget(QWidget):
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        super().__init__()

    def closeEvent(self, evt) -> None:
        assert isinstance(self.client.panel, ClientControlPanel)
        self.client.login.logInWindow.setEnabled(True)
        self.client.panel.controlPanel.setEnabled(True)
        self.client.setParams.setParamsUi.setEnabled(True)
        self.client.setParams.setParamsUi.close()


class ClientDialog:
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.errorDialogWindow = DialogWidget(client)
        self.errorDialogWindowContent = ErrorDialog()
        self.errorDialogWindowContent.setupUi(self.errorDialogWindow)
        self.errorDialogWindowContent.pushButton.clicked.connect(self.errorDialogWindow.close)

    def show_dialog(self, caution_str):
        assert isinstance(caution_str, str)
        self.errorDialogWindowContent.label.setText(
            "<html><head/><body><p align=\"center\"><span style=\" font-weight:600;\">" + caution_str + "</span></p></body></html>")
        self.errorDialogWindow.show()


class ClientLogIn:
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.IdList = [('Jim', '123456'), ('Tim', '654321')]

        self.logInWindow = QWidget()
        self.logInWindowContent = EngineerLogInUi()
        self.logInWindowContent.setupUi(self.logInWindow)

        self.logInWindowContent.logInButton.clicked.connect(client.login_request)

    def login_request(self):
        log_id = self.logInWindowContent.idEdit.text()
        log_passwd = self.logInWindowContent.passwdEdit.text()
        print(log_id, log_passwd)
        if (log_id, log_passwd) in self.IdList:
            return True
        else:
            return False

    def show_login(self):
        self.logInWindow.show()

    def close_login(self):
        self.logInWindowContent.idEdit.setText('')
        self.logInWindowContent.passwdEdit.setText('')
        self.logInWindow.close()


class ClientSetParams:
    class SetParamsWidget(QWidget):
        def __init__(self, client):
            super().__init__()
            assert isinstance(client, Client)
            self.client = client

        def closeEvent(self, evt) -> None:
            self.client.panel.controlPanel.setEnabled(True)

    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.setParamsUi = self.SetParamsWidget(client)
        self.setParamsUiContent = EngineerSetParamsUi()
        self.setParamsUiContent.setupUi(self.setParamsUi)
        self.setParamsUiContent.setButton.clicked.connect(self.set_params)

        group_one = QButtonGroup(self.setParamsUi)
        group_two = QButtonGroup(self.setParamsUi)
        group_one.addButton(self.setParamsUiContent.warmMode)
        group_one.addButton(self.setParamsUiContent.coldMode)
        group_two.addButton(self.setParamsUiContent.lowFan)
        group_two.addButton(self.setParamsUiContent.midFan)
        group_two.addButton(self.setParamsUiContent.highFan)
        self.setParamsUiContent.coldMode.setChecked(True)

    def check_params(self):
        if self.setParamsUiContent.maxTemp.value() < self.setParamsUiContent.minTemp.value() or \
                self.setParamsUiContent.defaultTemp.value() > self.setParamsUiContent.maxTemp.value() or \
                self.setParamsUiContent.defaultTemp.value() < self.setParamsUiContent.minTemp.value():
            self.setParamsUi.setEnabled(False)
            self.client.errorDialog.show_dialog("Wrong set of min-max temperature!")
            return False
        try:
            low_fee = float(self.setParamsUiContent.lowFee.text())
            mid_fee = float(self.setParamsUiContent.midFee.text())
            mid_fee = float(self.setParamsUiContent.highFee.text())
        except ValueError as e:
            self.client.errorDialog.show_dialog("Wrong format of fee input!")
            return False
        return True

    def show(self):
        self.client.panel.controlPanel.setEnabled(False)
        self.setParamsUi.show()

    def set_params(self):
        if self.check_params():
            flush_sock_pipe(self.client.panel.socketToServer)
            self.client.panel.socketToServer.send(
                struct.pack('!iiififffff', 0, 0,
                            self.client.setParams.setParamsUiContent.warmMode.isChecked(),
                            self.client.setParams.setParamsUiContent.defaultTemp.value(),
                            1 if self.client.setParams.setParamsUiContent.lowFan.isChecked() \
                                else 2 if self.client.setParams.setParamsUiContent.midFan.isChecked() else 3,
                            self.client.setParams.setParamsUiContent.minTemp.value(),
                            self.client.setParams.setParamsUiContent.maxTemp.value(),
                            float(self.client.setParams.setParamsUiContent.lowFee.text()),
                            float(self.client.setParams.setParamsUiContent.midFee.text()),
                            float(self.client.setParams.setParamsUiContent.highFee.text())))
            send_file('room_id.txt', self.client.panel.socketToServer)
            self.client.panel.controlPanel.setEnabled(False)
            self.client.setParams.setParamsUi.setEnabled(False)
            outcome = struct.unpack('!i', self.client.panel.socketToServer.recv(4))
            if outcome[0] == 0:
                self.client.errorDialog.show_dialog("Fail to set parameters")
            else:
                self.client.panel.controlPanelContent.startUpSys.setEnabled(True)
                self.client.errorDialog.show_dialog("Success to set parameters")


class ClientControlPanel:
    class PanelWidget(QWidget):
        def __init__(self, client):
            super().__init__()
            assert isinstance(client, Client)
            self.client = client

        def closeEvent(self, evt) -> None:
            # After log out from Control Panel, open the log-in window again
            self.client.login.show_login()
            self.client.panel.socketToServer.close()
            self.client.panel.socketToServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            pass

    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.controlPanel = self.PanelWidget(client)
        self.controlPanelContent = EngineerControllerPanelUi()
        self.controlPanelContent.setupUi(self.controlPanel)
        self.socketToServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.is_connected = False

        # Connect the signal and slots
        self.controlPanelContent.startUpSys.clicked.connect(self.start_up_system)
        self.controlPanelContent.setParams.clicked.connect(self.client.setParams.show)
        self.controlPanelContent.powerOn.clicked.connect(self.power_on)
        self.controlPanelContent.viewRoom.clicked.connect(self.show_view_status)
        self.controlPanelContent.sysShutDown.clicked.connect(self.sys_shutdown)
        self.controlPanelContent.logOut.clicked.connect(self.controlPanel.close)

    def socket_connect(self):
        try:
            # Todo: May change the address
            self.socketToServer.connect((socket.gethostname(), 8000))
        except ConnectionError as e:
            print(e)
            return False
        else:
            return True

    def try_to_connect_existed(self):
        # If a server process remained, trying to connect to it
        if os.path.exists('.is_open'):
            # If server process is running
            try:
                self.socketToServer.connect((socket.gethostname(), 8000))
            except ConnectionError as e:
                print("Fail to connect to existed server process:", e)
                # Shutdown the server process and remove this temp-file
                subprocess.run("kill -9 " + open(".is_open", 'r').readline() + '&>/dev/null', shell=True)
                subprocess.run("rm .is_open &>/dev/null", shell=True)
                self.is_connected = False
            else:
                # Success to connect to existed server process
                print("Connect to existed server process...")
                self.controlPanelContent.powerOn.setEnabled(False)
                self.is_connected = True
        else:
            print("No remained server process...")
            self.is_connected = False
        self.set_button_enabled()
        if self.is_connected:
            self.ini_after_connect()

    def disconnect(self):
        self.socketToServer.close()

    def unconnected_warn(self):
        self.controlPanel.setEnabled(False)
        self.client.errorDialog.show_dialog("Haven't connected to server yet!")

    def ini_after_connect(self):
        flush_sock_pipe(self.socketToServer)
        self.socketToServer.send(struct.pack('!iii', 3, 0, 0))
        is_start, is_set_params = struct.unpack('!ii', self.socketToServer.recv(8))
        if is_start:
            self.controlPanelContent.startUpSys.setEnabled(False)
            self.controlPanelContent.setParams.setEnabled(False)
            self.controlPanelContent.viewRoom.setEnabled(True)
        else:
            if not is_set_params:
                self.controlPanelContent.startUpSys.setEnabled(False)

    def start_up_system(self):
        flush_sock_pipe(self.socketToServer)
        self.socketToServer.send(struct.pack('!ii', 1, 0))
        outcome = struct.unpack('!i', self.socketToServer.recv(4))
        self.controlPanel.setEnabled(False)
        if outcome[0] == 0:
            self.client.errorDialog.show_dialog("Fail to start up system!")
        else:
            self.client.errorDialog.show_dialog("Success to start up system!")
            self.controlPanelContent.startUpSys.setEnabled(False)
            self.controlPanelContent.setParams.setEnabled(False)
            self.controlPanelContent.viewRoom.setEnabled(True)

    def power_on(self):
        self.controlPanel.setEnabled(False)
        if not self.try_to_connect_existed():
            srv_proc = subprocess.Popen("py server.py", shell=True)
            time.sleep(1)
            if not self.socket_connect():
                srv_proc.kill()
                print("Failed to connect to server...Reload the process and try again;")
                sys.exit(1)
            self.is_connected = True
            self.set_button_enabled()
            self.ini_after_connect()
            self.client.errorDialog.show_dialog("Server is running and connected!")
            self.controlPanelContent.powerOn.setEnabled(False)

    def show_panel(self):
        # Display the control-panel
        self.try_to_connect_existed()
        self.controlPanel.show()

    def show_view_status(self):
        if self.is_connected:
            self.controlPanel.setEnabled(False)
            self.client.statusView.show_view()
        else:
            self.unconnected_warn()

    def sys_shutdown(self):
        # Todo:
        subprocess.run("kill -15 " + open(".is_open").readline(), shell=True)
        subprocess.run("rm .is_open", shell=True)
        self.is_connected = False
        self.set_button_enabled()
        self.socketToServer.close()
        self.socketToServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    def status_request(self):
        if self.is_connected:
            # Todo: Do status-request
            # Using synchronized-manner first
            # Todo: Could un-synchronized-manner used?
            pass
        else:
            self.unconnected_warn()

    def set_button_enabled(self):
        self.controlPanelContent.powerOn.setEnabled(not self.is_connected)
        self.controlPanelContent.startUpSys.setEnabled(self.is_connected)
        self.controlPanelContent.setParams.setEnabled(self.is_connected)
        self.controlPanelContent.viewRoom.setEnabled(False)
        self.controlPanelContent.sysShutDown.setEnabled(self.is_connected)


class ClientViewStatus:
    class ViewStatusWidget(QWidget):
        def __init__(self, client):
            assert isinstance(client, Client)
            self.client = client
            super().__init__()

        def timerEvent(self, evt) -> None:
            self.client.statusView.request_room_status()
            pass

        def closeEvent(self, evt) -> None:
            # After close the ViewStatus Panel, enable the ControlPanel Again
            self.client.panel.controlPanel.setEnabled(True)
            self.client.statusView.viewWindow.killTimer(self.client.statusView.timer)

    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.viewWindow = self.ViewStatusWidget(client)
        self.viewWindowContent = EngineerViewStatusUi()
        self.viewWindowContent.setupUi(self.viewWindow)
        self.timer = None

        # Initialize the Room-ID list
        self.viewWindowContent.roomIDSelect.addItem("N/A")
        for room_id in client.roomIDList:
            self.viewWindowContent.roomIDSelect.addItem(room_id)
        self.viewWindowContent.roomIDSelect.setCurrentText('N/A')
        # Connect the signal and slots
        self.viewWindowContent.roomIDSelect.currentTextChanged.connect(self.request_room_status)
        self.viewWindowContent.quitViewButton.clicked.connect(self.viewWindow.close)

    def show_view(self):
        self.timer = self.viewWindow.startTimer(1000)
        self.viewWindow.show()

    def request_room_status(self):
        target_room_id = int(self.viewWindowContent.roomIDSelect.currentText())
        if self.viewWindowContent.roomIDSelect.currentText() == 'N/A':
            pass
        else:
            # Todo: Request the room designated by Selecting ComboBox
            try:
                self.client.panel.socketToServer.recv(1024, socket.MSG_DONTWAIT)
            except OSError:
                pass
            self.client.panel.socketToServer.send(
                struct.pack('!ii', 2, int(self.viewWindowContent.roomIDSelect.currentText())))
            cur_status = struct.unpack('!iiffifff', self.client.panel.socketToServer.recv(32))
            self.viewWindowContent.modeShow.setText("Warm" if cur_status[0] == 0 else "Cold")
            self.viewWindowContent.serviceStatus.setText(
                "STOP" if cur_status[1] == 0 else "RUN" if cur_status[1] == 1
                else "WAIT" if cur_status[1] == 2 else "PASUE")
            self.viewWindowContent.tagetTemperature.display(str(cur_status[2]))
            self.viewWindowContent.currrentTemperature.display(str(cur_status[3]))
            # Todo:
            self.viewWindowContent.fanSpeed.display(str(cur_status[4]))
            self.viewWindowContent.serviceTime.setText(str(cur_status[5]))
            self.viewWindowContent.curFee.display(str(cur_status[6])[0:4])
            self.viewWindowContent.accFee.display(str(cur_status[7])[0:4])


def check_server_process():
    if os.path.exists('.is_open'):
        return True
    else:
        return False
    pass


class Client:
    def __init__(self, ini_file_path):
        self.roomIDList = []
        with open(ini_file_path) as f:
            for item in f.readlines():
                self.roomIDList.append(item)

        self.login = ClientLogIn(self)
        self.setParams = ClientSetParams(self)
        self.panel = ClientControlPanel(self)
        self.errorDialog = ClientDialog(self)
        self.statusView = ClientViewStatus(self)

    def login_request(self):
        if self.login.login_request():
            # Password correct
            self.panel.show_panel()
            self.login.close_login()
        else:
            self.login.logInWindow.setEnabled(False)
            self.errorDialog.show_dialog("ID or Password Wrong!")


if __name__ == '__main__':
    clientProcess = QApplication([])
    clientWindow = Client("/home/zrg/PycharmProjects/GUI_For_SE/src/room_id.txt")
    clientWindow.login.logInWindow.show()
    clientProcess.exec_()
